<?php

namespace App\Service;

/**
 * PHP 富文本XSS过滤类
 *
 * @package XssHtml
 * @version 1.0.1
 * @link http://phith0n.github.io/XssHtml
 * @since 20140621
 * @copyright (c) Phithon All Rights Reserved
 *
 *【2019-02-15由ThinkSAAS继续完善修正部分问题】
 */
class XssHtml
{

    private $m_dom;
    private $m_xss;
    private $m_ok;
    private $m_AllowAttr = array(
      'title',
      'src',
      'href',
      'id',
      'class',
      'style',
      'width',
      'height',
      'alt',
      'target',
      'align',
      'type',
      'pluginspage',
      'wmode',
      'play',
      'loop',
      'menu',
      'allowscriptaccess',
      'allowfullscreen',
      'frameborder',
      'preload',
      'data-setup',
      'tabindex',
      'aria-live',
      'aria-label',
      'aria-hidden',
      'aria-haspopup',
      'role',
      'controls',
    );
    private $m_AllowTag  = array(
      'a',
      'img',
      'br',
      'strong',
      'b',
      'code',
      'pre',
      'p',
      'div',
      'em',
      'span',
      'h1',
      'h2',
      'h3',
      'h4',
      'h5',
      'h6',
      'table',
      'ul',
      'ol',
      'tr',
      'th',
      'td',
      'hr',
      'li',
      'u',
      'video',
      'audio',
      'source',
      'blockquote',
      'iframe',
      'embed',
    );

    /**
     * 构造函数
     *
     * @param string $html 待过滤的文本
     * @param string $charset 文本编码，默认utf-8
     * @param array $AllowTag 允许的标签，如果不清楚请保持默认，默认已涵盖大部分功能，不要增加危险标签
     */
    public function __construct($html, $charset = 'utf-8', $AllowTag = array())
    {
        $this->m_AllowTag = empty($AllowTag) ? $this->m_AllowTag : $AllowTag;
        $this->m_xss      = strip_tags($html, '<' . implode('><', $this->m_AllowTag) . '>');
        if (empty($this->m_xss)) {
            $this->m_ok = false;
            return;
        }

        $this->m_xss = "<meta http-equiv=\"Content-Type\" content=\"text/html;charset={$charset}\">" . $this->m_xss;
        $this->m_dom = new \DOMDocument();
        $this->m_ok  = @$this->m_dom->loadHTML($this->m_xss);
        // 某个变量
        $this->m_dom->strictErrorChecking = false;
    }

    /**
     * 获得过滤后的内容
     * $removeP 一般内容前后会生成p标签，如果是富文本则本来就有p标签，则不处理
     */
    public function getHtml($removeP = true)
    {
        if (!$this->m_ok) {
            return '';
        }

        $nodeList = $this->m_dom->getElementsByTagName('*');
        for($i = 0; $i < $nodeList->length; $i++) {
            $node = $nodeList->item($i);
            if (in_array($node->nodeName, $this->m_AllowTag)) {
                if (method_exists($this, "__node_{$node->nodeName}")) {
                    call_user_func(array($this, "__node_{$node->nodeName}"), $node);
                } else {
                    call_user_func(array($this, '__node_default'), $node);
                }
            }
        }

        $string = strip_tags($this->m_dom->saveHTML(), '<' . implode('><', $this->m_AllowTag) . '>');
        if ($removeP) {
            $string = substr($string, 4, -5);
        }

        return $string;
    }


    // 返回真实url
    private function __true_url($url)
    {
        /*
        if (preg_match('#^https?://.+#is', $url)) {
            return $url;
        }else{
            return 'http://' . $url;
        }
        */
        $href  = $url;
        $href1 = substr($href, 0, 1);
        $href4 = substr($href, 0, 4);
        $href7 = substr($href, 0, 7);
        $href8 = substr($href, 0, 8);

        if (in_array($href7, ['http://', 'mailto:']) || ($href8 == "https://") || ($href4 == "tel:") || in_array($href1, ['#', '/'])) {
            return $href;
        } else {
            return '';
        }
    }

    // 获取样式
    private function __get_style($node)
    {
        if ($node->attributes->getNamedItem('style')) {
            $style = $node->attributes->getNamedItem('style')->nodeValue;
            $style = str_replace('\\', ' ', $style);
            $style = str_replace(array('&#', '/*', '*/'), ' ', $style);
            $style = preg_replace('#e.*x.*p.*r.*e.*s.*s.*i.*o.*n#Uis', ' ', $style);
            return $style;
        } else {
            return '';
        }
    }

    // 获取链接
    private function __get_link($node, $att)
    {
        $link = $node->attributes->getNamedItem($att);
        if ($link) {
            return $this->__true_url($link->nodeValue);
            //return $link->nodeValue;
        } else {
            return '';
        }
    }

    // 设置属性
    private function __setAttr($dom, $attr, $val)
    {
        /*
        if (!empty($val)) {
            $dom->setAttribute($attr, $val);
        }
        */
        if (($attr == 'href' && $val == '') || ($attr && $val)) {
            $dom->setAttribute($attr, $val);
        }
    }

    // 设置默认属性
    private function __set_default_attr($node, $attr, $default = '')
    {
        $o = $node->attributes->getNamedItem($attr);
        if ($o) {
            $this->__setAttr($node, $attr, $o->nodeValue);
        } else {
            $this->__setAttr($node, $attr, $default);
        }
    }

    // 常用属性
    private function __common_attr($node)
    {
        $list = array();
        foreach($node->attributes as $attr) {
            if (!in_array(
              $attr->nodeName,
              $this->m_AllowAttr
            )) {
                $list[] = $attr->nodeName;
            }
        }
        foreach($list as $attr) {
            $node->removeAttribute($attr);
        }
        $style = $this->__get_style($node);
        $this->__setAttr($node, 'style', $style);
        $this->__set_default_attr($node, 'title');
        $this->__set_default_attr($node, 'id');
        $this->__set_default_attr($node, 'class');
    }

    // img标签
    private function __node_img($node)
    {
        $this->__common_attr($node);

        $this->__set_default_attr($node, 'src');
        $this->__set_default_attr($node, 'width');
        $this->__set_default_attr($node, 'height');
        $this->__set_default_attr($node, 'alt');
        $this->__set_default_attr($node, 'align');

    }

    // a标签
    private function __node_a($node)
    {
        $this->__common_attr($node);
        $href = $this->__get_link($node, 'href');
        $this->__setAttr($node, 'href', $href);
        //$this->__set_default_attr($node, 'target', '_blank');
    }

    // embed标签
    private function __node_embed($node)
    {
        $this->__common_attr($node);
        //$link = strtolower($this->__get_link($node, 'src'));
        $link = $this->__get_link($node, 'src');


        $this->__setAttr($node, 'src', $link);
        $this->__setAttr($node, 'allowscriptaccess', 'never');
        $this->__set_default_attr($node, 'width');
        $this->__set_default_attr($node, 'height');
    }

    // iframe标签
    private function __node_iframe($node)
    {
        $this->__common_attr($node);
        //$link = strtolower($this->__get_link($node, 'src'));

        $link = $this->__get_link($node, 'src');

        $this->__setAttr($node, 'src', $link);
    }

    // 标签节点默认值
    private function __node_default($node)
    {
        $this->__common_attr($node);
    }
}

